package io.gitee.zhangbinhub.admin.authentication

import cn.dev33.satoken.context.model.SaRequest
import cn.dev33.satoken.oauth2.SaOAuth2Manager
import cn.dev33.satoken.oauth2.consts.SaOAuth2Consts
import cn.dev33.satoken.oauth2.data.model.AccessTokenModel
import cn.dev33.satoken.oauth2.data.model.request.RequestAuthModel
import cn.dev33.satoken.oauth2.exception.SaOAuth2Exception
import cn.dev33.satoken.oauth2.granttype.handler.SaOAuth2GrantTypeHandlerInterface
import cn.dev33.satoken.stp.StpUtil
import cn.dev33.satoken.stp.parameter.SaLoginParameter
import cn.dev33.satoken.util.SaResult
import io.gitee.zhangbinhub.acp.boot.exceptions.WebException
import io.gitee.zhangbinhub.acp.boot.http.HttpStatus
import io.gitee.zhangbinhub.admin.component.AuthPasswordEncrypt
import io.gitee.zhangbinhub.admin.component.AuthUserService
import io.gitee.zhangbinhub.admin.constant.AcpConstant
import io.gitee.zhangbinhub.admin.constant.OauthConstant
import io.gitee.zhangbinhub.admin.entity.User
import io.gitee.zhangbinhub.admin.service.ApplicationService
import io.gitee.zhangbinhub.admin.tools.TokenTools
import io.gitee.zhangbinhub.admin.vo.TokenUserInfoVo
import org.noear.solon.annotation.Component

@Component
class UserPasswordGrantTypeHandler(
    private val applicationService: ApplicationService,
    private val authUserService: AuthUserService,
    private val authPasswordEncrypt: AuthPasswordEncrypt
) : SaOAuth2GrantTypeHandlerInterface {
    override fun getHandlerGrantType(): String = OauthConstant.granterUserPassword

    @Throws(WebException::class, SaOAuth2Exception::class)
    override fun getAccessToken(req: SaRequest, clientId: String, scopes: MutableList<String>): AccessTokenModel {
        val username: String = req.getParamNotNull(SaOAuth2Consts.Param.username)
        val password: String = req.getParamNotNull(SaOAuth2Consts.Param.password)
        val user = this.loginByUsernamePassword(clientId, username, password)
        val loginId = StpUtil.getLoginIdDefaultNull()
        if (loginId == null) {
            throw SaOAuth2Exception("登录失败")
        } else {
            val ra = RequestAuthModel()
            ra.clientId = clientId
            ra.loginId = loginId
            ra.scopes = scopes
            return SaOAuth2Manager.getDataGenerate().generateAccessToken(ra, true) { atm ->
                atm.grantType = OauthConstant.granterUserPassword
                atm.extraData[AcpConstant.tokenUserinfo] = TokenTools.encryptUserInfo(
                    TokenUserInfoVo(
                        appId = clientId,
                        id = user.id,
                        loginNo = user.loginNo,
                        name = user.name,
                        mobile = user.mobile,
                        loginTime = System.currentTimeMillis()
                    )
                )
                atm.extraData[AcpConstant.tokenPermissions] = authUserService.getPermissionList(loginId)
                atm.extraData[AcpConstant.tokenRoles] = authUserService.getRoleList(loginId)
            }
        }
    }

    @Throws(WebException::class)
    private fun loginByUsernamePassword(clientId: String, username: String, password: String): User {
        try {
            val user = authUserService.loadUserByUsername(username)
            if (authPasswordEncrypt.matches(password, user.password)) {
                StpUtil.logout(username, clientId)
                StpUtil.login(
                    username, SaLoginParameter().setDeviceType(clientId).setTimeout(
                        applicationService.getApp(clientId)?.refreshTokenValiditySeconds?.toLong()
                            ?: throw SaOAuth2Exception("client_id 无效")
                    )
                )
                SaResult.ok()
                authUserService.clearPasswordErrorTime(username)
                return user
            } else {
                authUserService.storePasswordErrorTime(username)
                throw SaOAuth2Exception("账号或密码错误")
            }
        } catch (e: Exception) {
            throw WebException(HttpStatus.UNAUTHORIZED, e.message)
        }
    }
}